Lazy loaded image
🗒️Golang 网络编程
字数 4793阅读时长 12 分钟
2025-6-16
2025-6-16
type
status
date
slug
summary
tags
category
password

互联网协议介绍

互联网的核心是一系列协议,总称为互联网协议(Internet Protocol Suite),正是这一些协议规定了电脑如何连接和组网。我们理解了这些协议,就理解了互联网的原理。由于这些协议太过庞大和复杂,没有办法在这里一概而全,只能介绍一下我们日常开发中接触较多的几个协议。

TCP 编程

TCP 协议

TCP/IP (Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题,下面我们来看一个简单的 TCP 服务端和 TCP 客户端

TCP 服务端

一个TCP服务端可以同时连接很多个客户端,例如世界各地的用户使用自己电脑上的浏览器访问京东,淘宝等。因为Go语言中创建多个goroutine实现并发非常方便和高效,所以我们可以每建立一次链接就创建一个goroutine去处理,TCP 服务端程序处理的流程如下
TCP 服务端程序处理流程

监听端口

接收客户端请求建立连接

创建 goroutine 处理连接

下面是一段 TCP 服务端的代码

TCP 客户端

一个TCP客户端进行TCP通信的流程如下

建立与服务端的链接

进行数据收发

关闭链接

使用 Go 语言的 net 包实现的 TCP 客户端代码如下
然后我们分别运行 server 和 client

UDP 编程

UDP 协议

UDP 协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection 开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP 协议的实时性比较好,通常用于视频直播相关领域,同样的,我们编写一个 UDP 服务端和 UDP 客户端

UDP 服务端

package main​import ( "fmt" "net")​func main() { // 创建一个 UDP 的监听 listener, err := net.ListenUDP("udp", &net.UDPAddr{ IP:   net.ParseIP("127.0.0.1"), Port: 8080, }) if err != nil { fmt.Printf("Listen failed, err: %s", err) return }​ // 关闭监听 defer func(listener *net.UDPConn) { err := listener.Close() if err != nil { fmt.Printf("Close failed, err: %s", err) } }(listener)​ // 接收数据 for { // 创建一个缓冲区 var buf [1024]byte​ // 从 UDP 连接中读取数据 n, addr, err := listener.ReadFromUDP(buf[:]) if err != nil { fmt.Printf("ReadFromUDP failed, err: %s", err) continue }​ // 打印数据 fmt.Printf("收到客户端发来的数据: %s, 客户端地址: %s", string(buf[:n]), addr)​ // 回复客户端 _, err = listener.WriteToUDP(buf[:n], addr) if err != nil { fmt.Printf("WriteToUDP failed, err: %s", err) continue } }}

UDP 客户端

package main​import ( "fmt" "net")​func main() { // 创建一个 UDP 的连接 conn, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP:   net.ParseIP("127.0.0.1"), Port: 8080, }) if err != nil { fmt.Printf("DialUDP failed, err: %s", err) return }​ // 关闭连接 defer func(conn *net.UDPConn) { err := conn.Close() if err != nil { fmt.Printf("Close failed, err: %s", err) } }(conn)​ // 创建需要发送的数据 SendData := []byte("Hello, UDP!")​ // 发送数据 _, err = conn.Write(SendData) if err != nil { fmt.Printf("Write failed, err: %s", err) return }​ // 创建一个缓冲区 data := make([]byte, 4096) n, remoteAddr, err := conn.ReadFromUDP(data) if err != nil { fmt.Printf("ReadFromUDP failed, err: %s", err) return }​ // 打印数据 fmt.Printf("收到服务器发来的数据: %s, 服务器地址: %s", string(data[:n]), remoteAddr)}
最终我们来分别启动服务端和客户端并发送数据

TCP 黏包与处理

TCP 数据传递模式是流模式,在保持长连接的时候可以进行多次的收和发,而 粘包可发生在发送端也可发生在接收端

TCP 黏包示例

package main​import ( "bufio" "fmt" "io" "net")​// 创建一个 process 用来模拟黏包的逻辑func process(conn net.Conn) { // 关闭连接 defer func(conn net.Conn) { err := conn.Close() if err != nil { return } }(conn)​ // 创建一个 reader reader := bufio.NewReader(conn)​ // 创建一个缓冲区 var buf [1024]byte​ // 循环读取数据 for { n, err := reader.Read(buf[:]) if err == io.EOF { break } if err != nil { fmt.Println("Read from client failed, err:", err) break }​ // 将读取到的数据转换为字符串 dataToStr := string(buf[:n])​ fmt.Println("收到客户端发来的数据:", dataToStr) }}​func main() { // 创建一个 TCP 的监听 listen, err := net.Listen("tcp", "127.0.0.1:8080") if err != nil { fmt.Println("listen failed, err:", err) return }​ // 关闭监听 defer func(listen net.Listener) { err := listen.Close() if err != nil { return } }(listen)​ // 循环等待客户端连接 for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed, err:", err) continue }​ // 开启一个 goroutine 处理连接 go process(conn) }}
分别启动服务端和客户端
可以看到,客户端是分了 20 次发送的数据,但是服务端却没有成功打印出 20 次的数据,这个就叫做黏包

TCP 黏包解决方案

出现 黏包 的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入'包尾' 内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
我们可以自己定义一个属于自己的协议,比如数据包的前 4 个字节为包头,里面存储的是发送的数据的长度。
接下来在服务端和客户端分别使用上面定义的 util 包的 DecodeEncode函数处理数据。
再次启动服务端和客户端来测试
可以看到,这个时候就符合我们的要求了,循环接收了 20 次数据了,而且黏包的情况也不再有了

HTTP 编程

Web 服务器工作流程

客户机通过 TCP/IP 协议建立到服务器的TCP连接客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理动态内容,并将处理得到的数据返回给客户端客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果

HTTP 协议

超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议HTTP协议通常承载于TCP协议之上

HTTP 服务端

package main​import ( "fmt" "net/http")​// 创建一个 Handler 处理 HTTP 请求func handler(w http.ResponseWriter, r *http.Request) { fmt.Printf("%s 连接成功", r.RemoteAddr) fmt.Printf("%s 当前请求方式", r.Method) fmt.Printf("%s 当前请求路径", r.URL.Path) fmt.Printf("%s 当前请求头", r.Header) fmt.Printf("%s 当前请求参数", r.URL.Query()) fmt.Printf("%s 当前请求体", r.Body)​ write, err := w.Write([]byte("Hello, World!")) if err != nil { return } fmt.Printf("%s 当前响应体长度", write)}​func main() { // 创建一个 HTTP 服务器 server := http.Server{ Addr:    "127.0.0.1:8080", Handler: http.HandlerFunc(handler), }​ // 启动 HTTP 服务器 if err := server.ListenAndServe(); err != nil { fmt.Println("ListenAndServe failed, err:", err) return }}

HTTP 客户端

package main​import ( "fmt" "io" "net/http")​func main() { // 请求 HTTP 服务器 resp, _ := http.Get("http://127.0.0.1:8080")​ // 关闭连接 defer func(Body io.ReadCloser) { err := Body.Close() if err != nil { return } }(resp.Body)​ // 打印请求的状态码 fmt.Println(resp.StatusCode)​ // 打印请求的响应头 fmt.Println(resp.Header)​ // 创建一个 Buffer buf := make([]byte, 1024)​ // 循环接收服务器的响应体 for { // 接收服务端信息 n, err := resp.Body.Read(buf) if err != nil && err != io.EOF { fmt.Println(err) return } else { fmt.Println("读取完毕") res := string(buf[:n]) fmt.Println(res) break } }}
依旧是分别启动服务端和客户端

WebSocket 编程

WebSocket 是什么

WebSocket 是一种在单个TCP连接上进行全双工通信的协议WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输需要安装第三方包(go get -u -v github.com/gorilla/websocket)

WebSocket 实战案例

package main​import ( "fmt" "github.com/gorilla/websocket" "net/http")​// upgrader 是用于 websocket 的升级器, 将 http 请求升级为 websocket 请求var upgrader = websocket.Upgrader{ ReadBufferSize:  1024, WriteBufferSize: 1024,}​// handler 处理 websocket 请求func handler(w http.ResponseWriter, r *http.Request) { // 升级请求为 websocket 请求 conn, err := upgrader.Upgrade(w, r, nil) if err != nil { http.Error(w, "Failed to upgrade connection", http.StatusBadRequest) return }​ // 循环读取客户端发送的消息 for { // 读取消息 msgType, msg, err := conn.ReadMessage() if err != nil { return }​ // 打印消息 fmt.Printf("%s Send %s\n", conn.RemoteAddr(), string(msg))​ // 将消息发送客户端 if err = conn.WriteMessage(msgType, msg); err != nil { return } }}​// root 处理静态文件请求func root(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html")}​func main() { // 创建 http 服务器 http.HandleFunc("/", root) http.HandleFunc("/ws", handler)​ if err := http.ListenAndServe(":80", nil); err != nil { fmt.Println("ListenAndServe:", err) }}
然后我们启动 main.go,然后打开网页访问 http://127.0.0.1/ 它会打开一个网页,然后你就可以进行 WebSocket 通信了,然后你每发送一个消息,服务端的 Console 就会打印如下消息
上一篇
NFS-CSI的使用
下一篇
手搓一个 MCP Server(三种模式)